// exception.cc 
//    Entry point into the Nachos kernel from user programs.
//    There are two kinds of things that can cause control to
//    transfer back to here from user code:
//
//    syscall -- The user code explicitly requests to call a procedure
//    in the Nachos kernel.  Right now, the only function we support is
//    "Halt".
//
//    exceptions -- The user code does something that the CPU can't handle.
//    For instance, accessing memory that doesn't exist, arithmetic errors,
//    etc.  
//
//    Interrupts (which can also cause control to transfer from user
//    code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include <stdio.h>        // FA98
#include <string.h>
#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "addrspace.h"   // FA98
#include "sysdep.h"   // FA98

// begin FA98

static int SRead(int addr, int size, int id);
static void SWrite(char *buffer, int size, int id);

// end FA98

static void SExit(int status);
static void ProcessCreator(int nullArg);
bool setThreadFirst(Thread * spawn);
static int tCount = -1;

//----------------------------------------------------------------------
// ExceptionHandler
//     Entry point into the Nachos kernel.  Called when a user program
//    is executing, and either does a syscall, or generates an addressing
//    or arithmetic exception.
//
//     For system calls, the following is the calling convention:
//
//     system call code -- r2
//        arg1 -- r4
//        arg2 -- r5
//        arg3 -- r6
//        arg4 -- r7
//
//    The result of the system call, if any, must be put back into r2. 
//
// And don't forget to increment the pc before returning. (Or else you'll
// loop making the same system call forever!
//
//    "which" is the kind of exception.  The list of possible exceptions 
//    are in machine.h.
//----------------------------------------------------------------------

void
ExceptionHandler(ExceptionType which)
{
    int type = machine->ReadRegister(2);

    int arg1 = machine->ReadRegister(4);
    int arg2 = machine->ReadRegister(5);
    int arg3 = machine->ReadRegister(6);
    int Result;
    int i, j;
    char *ch = new char [500];
    IntStatus intState = IntOff;
    char *fileName = new char[100];
    int prcsID = 0;
    
    switch ( which )
    {
    case NoException :
        break;
    case SyscallException :

        // for debugging, in case we are jumping into lala-land
        // Advance program counters.
        machine->registers[PrevPCReg] = machine->registers[PCReg];
        machine->registers[PCReg] = machine->registers[NextPCReg];
        machine->registers[NextPCReg] = machine->registers[NextPCReg] + 4;

        switch ( type )
        {

        case SC_Halt :
            printf ("\nSC_Halt called via process #%d.\n", currentThread->space->GetSpaceId());
            DEBUG('t', "Shutdown, initiated by user program.\n");
            interrupt->Halt();
            break;

            
        case SC_Read :
            printf ("\nSC_Read called via process #%d.\n", currentThread->space->GetSpaceId());
            if (arg2 <= 0 || arg3 < 0){
                printf("\nRead 0 byte.\n");
            }
            Result = SRead(arg1, arg2, arg3);
            machine->WriteRegister(2, Result);
            DEBUG('t',"Read %d bytes from the open file(OpenFileId is %d)",
            arg2, arg3);
            printf ("SC_Read complete.\n");
            break;

        case SC_Write :
            printf ("\nSC_Write called via process #%d.\n", currentThread->space->GetSpaceId());
            for (j = 0; ; j++) {
                if(!machine->ReadMem((arg1+j), 1, &i))
                    j=j-1;
                else{
                    ch[j] = (char) i;
                    if (ch[j] == '\0') 
                        break;
                }
            }
            if (j == 0){
                printf("\nWrite 0 byte.\n");
                // SExit(1);
            } else {
                DEBUG('t', "\nWrite %d bytes from %s to the open file(OpenFileId is %d).", arg2, ch, arg3);
                SWrite(ch, j, arg3);
            }
            printf ("SC_Write complete.\n");
            break;

        /* SC_Exec 
                
        This system call takes the filename of a process to be run
        as the argument.
        
        It performs the following actions:
        
        1. Allocation of address space
        2. Checking to see if there is space to run the process
        3. Creation of a new thread/assignment of address space
        4. Assignment of Process ID # for later tracking
        5. Initialization of thread's local state and registers
        6. Adding thread w/ new process to ready queue
        
        Interrupts are disabled when context switches are performed.
        
        -Eric Killen(ekk5147) */    
            
        case SC_Exec :
        {
            printf ("\nSC_Exec called via process #%d.\n", currentThread->space->GetSpaceId());
            //init filename string
            memset(fileName, 0, 100);
            
            //populate filename string(using shard of SC_Write code)
            for (j = 0; ; j++) {
                if(!machine->ReadMem((arg1+j), 1, &i))
                    j=j-1;
                else{
                    fileName[j] = (char) i;
                    if (fileName[j] == '\0') 
                        break;
                }
            }
            if (j == 0)
                break;
            else 
            
            prcsID = Exec(fileName);
            
            machine->WriteRegister(2, prcsID);
            
            interrupt->Enable();        
            currentThread->Yield(); //allow new process to initialize
            intState = interrupt->SetLevel(IntOff);
            
            printf ("SC_Exec(process #%d) complete.\n", currentThread->space->GetSpaceID());
        }    
            break;
        
    /* SC_Exit 
                
        This syscall safely terminates the calling user process. It accepts
        one of two integer arguments to indicate whether it was an intended 
    termination.
        It performs the following actions:
    
    1.  Check to see if the calling process is the main thread
    1a. If so, said process is placed in a busy-waiting loop until there 
        are no longer any other user processes running
    2.  Ensure the parent of the calling thread is both awoken and placed at
        the front of the ready queue  
        3.  Indicate to the user whether the process' termination is normal or 
        abnormal
        4.  Call Finish() on the calling process' thread
    
    Interrupts are disabled when context switches are performed.
    
        -Eric Killen(ekk5147) */  
    
        case SC_Exit:
            printf ("\nSC_Exit called via process #%d.\n", currentThread->space->GetSpaceId());
            SExit(arg1);
            break;
        
    /* SC_Exit 
                
        This syscall prevents the calling thread from taking further action until
    the thread it called for by ID has completed.
    
    It performs the following actions:
    
    1.  Checks to see if the passed process ID matches a thread in the active
        process list & is one of the calling process' children
    1a. If not, simply returns with a -1 value
    2.  Checks if the calling thread is the main thread
    2a. If true, has the thread yield
    2b. If not true, puts the thread to sleep(main thread cannot be put to sleep).
        SC_Exit will ensure the thread is appropriately awoken when its child 
        process terminates, as it wakes all parent processes of what calls it.
        
    Interrupts are disabled when context switches are performed.
    
        -Eric Killen(ekk5147) */      
        
    case SC_Join:
        printf ("\nSC_Join called via process #%d.\n", currentThread->space->GetSpaceId());
        Join(arg1);
        
    /* SC_Yield
    
    This syscall allows the calling user process to yield its execution to another process.
    
    This process simply calls Yield.
    
    Interrupts are disabled when context switches are performed.
    
        -Eric Killen(ekk5147) */
        case SC_Yield:
            printf ("\nSC_Yield called via process #%d.\n", currentThread->space->GetSpaceId());
            Yield();
            break;
            
        default :
            //Unprogrammed system calls end up here
            break;
        }         
        break;

    case ReadOnlyException :
        puts ("ReadOnlyException");
        if (currentThread->getName() == "main")
        SExit(1);
        break;
    case BusErrorException :
        puts ("BusErrorException");
        if (currentThread->getName() == "main")
        SExit(1);
        break;
    case AddressErrorException :
        puts ("AddressErrorException");
        if (currentThread->getName() == "main")
        SExit(1);
        break;
    case OverflowException :
        puts ("OverflowException");
        if (currentThread->getName() == "main")
        SExit(1);
        break;
    case IllegalInstrException :
        puts ("IllegalInstrException");
        if (currentThread->getName() == "main")
        SExit(1);
        break;
    case NumExceptionTypes :
        puts ("NumExceptionTypes");
        if (currentThread->getName() == "main")
        SExit(1);
        break;

        default :
        break;
    }
    delete [] ch;
    delete [] fileName;
}


static int SRead(int addr, int size, int id)  //input 0  output 1
{
    char buffer[size+10];
    int num,Result;

    //read from keyboard, try writing your own code using console class.
    if (id == 0)
    {
        scanf("%s",buffer);

        num=strlen(buffer);
        if(num>(size+1)) {

            buffer[size+1] = '\0';
            Result = size+1;
        }
        else {
            buffer[num+1]='\0';
            Result = num + 1;
        }

        for (num=0; num<Result; num++)
        {  machine->WriteMem((addr+num), 1, (int) buffer[num]);
            if (buffer[num] == '\0')
            break; }
        return num;

    }
    //read from a unix file, later you need change to nachos file system.
    else
    {
        for(num=0;num<size;num++){
            Read(id,&buffer[num],1);
            machine->WriteMem((addr+num), 1, (int) buffer[num]);
            if(buffer[num]=='\0') break;
        }
        return num;
    }
}



static void SWrite(char *buffer, int size, int id)
{
    //write to terminal, try writting your own code using console class.
    if (id == 1)
    printf("%s", buffer);
    //write to a unix file, later you need change to nachos file system.
    if (id >= 2)
    WriteFile(id,buffer,size);
}

// end FA98

/* Exec
Primary executor of the steps described in SC_Exec.
It accepts a filename argument, which is provided 
prior to the function call by reading in the char buffer.

This function: 
*Creates a thread with a unique integer identifier,
*Allocates an address space corresponding to it by filename
 if possible,
*Marks the created thread and its creator as child and parent
 respectively,
*Forks the thread to an initialization/run process,
*Ensures the process will be the next thread called on context
 switch,
*Yields once to allow the process to allocate, 
*And returns to the main process for register writing after
 ensuring the created process would be the next thread called on
 context switch again.
 
 Interrupts are disabled when context switches are performed.
 
 -Eric Killen(ekk5147)*/

SpaceId Exec(char *name)
{
    OpenFile *executable;
    AddrSpace *space;
    IntStatus intState = IntOff;
    Thread *t;
    ListElement * tInfo;

    char *tNameID = new char[1];
    
    tCount++;
    tNameID[0] = '0' + tCount;

    
    t = new Thread(tNameID);
    t->SetParent(currentThread);
    currentThread->AddChild(t, t->getName());

    //use filename string to open actual file and init address space
    executable = fileSystem->Open(name);
    if (!executable)
        Exit(1);
    space = new AddrSpace(executable);
    delete executable;        
    //*TODO* check if there is space to run new process *TODO*
            

    //give new thread the allocated address space
    t->space = space;       
            
    //initialize registers/state and start new process
    t->Fork(ProcessCreator, 0);
    setThreadFirst(t);
    
    interrupt->Enable();        
    currentThread->Yield(); //allow new process to initialize
    intState = interrupt->SetLevel(IntOff);

    setThreadFirst(t);
    return(t->space->GetSpaceId());
} 

/* Join

Primary executor of the steps described in
SC_Join. Literally performs the syscall's functionality.

Interrupts are disabled when context switches are performed.

-Eric Killen(ekk5147)*/

int Join(SpaceId id)
{
    IntStatus intState = IntOff;
    if (currentThread->isChild(id) && active->Find(id))    
    {    
        if (currentThread->space->GetSpaceId() != 0)
    {
            interrupt->Enable();
            currentThread->Sleep();
            intState = interrupt->SetLevel(IntOff);
    }
    else
        Yield();
    return 0;    
    }
    else {return -1;}
    
} 

/* Exit

Primary executor of the steps described in
SC_Exit. Literally performs the syscall's functionality.

Interrupts are disabled when context switches are performed.

-Eric Killen(ekk5147)*/
static void SExit(int status)
{

    IntStatus intState = IntOff;

    
    while(currentThread->space->GetSpaceId() == 0 && !active->IsProcEmpty())
    {    
        interrupt->Enable();
        currentThread->Yield();
    intState = interrupt->SetLevel(IntOff); 
    }
    if ((currentThread->GetParent()) && (currentThread->GetParent()->GetStatus() == BLOCKED))
    {    
        setThreadFirst(currentThread->GetParent());
    }
       
    if (status == 1)
        printf("Process exited abnormally!\n");
    else
        printf("Process exited gracefully.\n");

    interrupt->Enable();
    currentThread->Finish();
    intState = interrupt->SetLevel(IntOff); 
    
}  

/* Yield
A simple currentThread->Yield call that is accessible
by user processes.

Interrupts are disabled when context switches are performed.

-Eric Killen(ekk5147) */
void Yield()
{
    IntStatus intState = IntOff;
    interrupt->Enable();
    currentThread->Yield();
    intState = interrupt->SetLevel(IntOff);
}

/* ProcessCreator

This method accepts a useless integer argument and initializes a 
new user process. To be used in tandem with Exec().

*Initially yields so that the process exists but is not initialized yet
*When is given control a second time, initializes the state and registers
 of the process, then runs

Interrupts are disabled when context switches are performed.

-Eric Killen(ekk5147) */
static void ProcessCreator(int nullArg)
{
    IntStatus intState = IntOff;
    Thread * owner;
     
    owner = currentThread->GetParent();
    setThreadFirst(owner);
    
    interrupt->Enable();
    currentThread->Yield();
    intState = interrupt->SetLevel(IntOff);

    currentThread->space->RestoreState();
    currentThread->space->InitRegisters();
    
    machine->Run();
    ASSERT(FALSE);
}


/* SetThreadFirst

This method takes the calling thread pointer
and ensures that it becomes the next thread that will
be executed in the ready queue.

After validating the passed process pointer's existence, it
flushes the ready queue out and appends that pointer to the queue
first, then places all removed members of the queue back in.

-Eric Killen(ekk5147) */
bool setThreadFirst(Thread * spawn)
{
    List thList;
    Thread * temp;
    int i = 0;
    if(spawn != NULL)
    {
        temp = scheduler->FindNextToRun();
        while(temp != NULL)
        {
            thList.Append(temp);
            i++;
            temp = scheduler->FindNextToRun();
        }
        
        scheduler->ReadyToRun(spawn);
        while(i > 0)
        {
            temp = (Thread *) thList.Remove();
            if(temp != spawn)
                scheduler->ReadyToRun(temp);
            i--;
        }
        return true;
    }
    else
        return false;

}
